/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2007, 2008 University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/queue.h"
#include "ns3/simulator.h"
#include "ns3/mac48-address.h"
#include "ns3/llc-snap-header.h"
#include "ns3/error-model.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/uinteger.h"
#include "ns3/pointer.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/ppp-header.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/drop-tail-queue.h"
#include "traffic-shaper-net-device.h"

NS_LOG_COMPONENT_DEFINE ("TrafficShaperNetDevice");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (TrafficShaperNetDevice);


TypeId 
PathShaperState::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::PathShaperState")
    .SetParent<Object> ()
 
  ;
  return tid;
}
 
PathShaperState::PathShaperState()
:pathQueue(0)
{
}

PathShaperState::~PathShaperState()
{
}


TypeId 
TrafficShaperNetDevice::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::TrafficShaperNetDevice")
    .SetParent<PointToPointNetDevice> ()
    .AddAttribute ("Buffer", 
                   "The maximum number of bytes accepted by the buffers of all the shapers",
                   UintegerValue (62500),
                   MakeUintegerAccessor (&TrafficShaperNetDevice::m_bufferSize),
                   MakeUintegerChecker<uint32_t> ())
    .AddConstructor<TrafficShaperNetDevice> ();
    ;

  return tid;
}


TrafficShaperNetDevice::TrafficShaperNetDevice () 
{
  NS_LOG_FUNCTION (this);
}

TrafficShaperNetDevice::~TrafficShaperNetDevice ()
{
  NS_LOG_FUNCTION_NOARGS ();
}



  DataRate 
TrafficShaperNetDevice::SetDataRate(DataRate r, int idx)
{
  Ptr<PathShaperState> pss = m_pathShapersMap[idx];
  if (pss)
    {
      if (m_bps > r)
        {
          pss->pathMaxRate = r;
        }
      else 
        {
          pss->pathMaxRate = m_bps;
        }

      return pss->pathMaxRate;
    }
  else 
    {
      return AddPathShaper (idx);
    }
}
  DataRate 
TrafficShaperNetDevice::AddPathShaper (int idx)
{
  //Configure the shaper
  Ptr<PathShaperState> pss = Create<PathShaperState>(); 
  pss->pathMaxRate = m_bps;
  pss->pathQueueState = LEAKING;

  ObjectFactory queueFactory;
  queueFactory.SetTypeId ("ns3::DropTailQueue");
  queueFactory.Set ("MaxBytes",UintegerValue(m_bufferSize) );
  Ptr<Queue> queueA = queueFactory.Create<Queue> ();


  Ptr<DropTailQueue> queue = DynamicCast<DropTailQueue> (queueA);
  pss->pathQueue = queue;
  //PathShaperState * pssp = &pss; 
  //Add the shaper to the list
  m_pathShapersMap[idx]= pss;
  LeakState l =  m_pathShapersMap[idx]->pathQueueState;
  NS_ASSERT(l==LEAKING); 
  return pss->pathMaxRate;
}

  bool 
TrafficShaperNetDevice::Send(Ptr<Packet> packet, const Address &dest, uint16_t protocolNumber)
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_LOG_LOGIC ("p=" << packet << ", dest=" << &dest);
  NS_LOG_LOGIC ("UID is " << packet->GetUid ());
  //std::cout << "Traffic Shaper \n";
  PathShaperTag tag;
  if (packet->PeekPacketTag(tag))
  {
  int idx = tag.GetPathId();
  //
  // If IsLinkUp() is false it means there is no channel to send any packet 
  // over so we just hit the drop trace on the packet and return an error.
  //
  if (IsLinkUp () == false)
    {
  
      //std::cout << "Linkdown \n";

      m_macTxDropTrace (packet);
      return false;
    }

  //
  // Stick a point to point protocol header on the packet in preparation for
  // shoving it out the door.
  //
  AddHeader(packet, protocolNumber);

 
  Ptr<PathShaperState> pss = m_pathShapersMap[idx];
  //std::cout << "Traffic Shaper \n";
  if(pss == 0) AddPathShaper (idx); 
  LeakState l = pss->pathQueueState;
  if (l == LEAKING) 
    {  

      //std::cout << "Inside Leaking \n";
      // 
      // Even if the transmitter is immediately available, we still enqueue and
      // dequeue the packet to hit the tracing hooks.
      //
      Ptr <DropTailQueue> pssQueue = pss->pathQueue;
      if (pssQueue->Enqueue (packet) == true)
        {
          //std::cout << "Queued \n";
          packet = pss->pathQueue->Dequeue ();
          return LeakStart (packet, idx);
        }
      else
        {
                   return false;
        }
    }
  else
    {
      return pss->pathQueue->Enqueue(packet);
    }
  }
  else
  {
     return PointToPointNetDevice::Send(packet, dest, protocolNumber);
  }
}

bool
TrafficShaperNetDevice::LeakStart (Ptr<Packet> packet, int idx)
{
  NS_LOG_FUNCTION_NOARGS ();
  NS_LOG_LOGIC ("UID is " << packet->GetUid ());
    
  //std::cout << "Leak Start \n";
  //
  // If IsLinkUp() is false it means there is no channel to send any packet 
  // over so we just hit the drop trace on the packet and return an error.
  //
  if (IsLinkUp () == false)
    {
      return false;
    }

  Ptr<PathShaperState> pss = m_pathShapersMap[idx];
  NS_ASSERT_MSG(pss->pathQueueState == LEAKING, "Must be READY to leak");
  pss->pathQueueState = NLEAKING;

  Time txTime = Seconds (pss->pathMaxRate.CalculateTxTime(packet->GetSize()));
  //std::cout << pss->pathMaxRate.GetBitRate() << "\n";
  Time txCompleteTime = txTime;

  NS_LOG_LOGIC ("Schedule TransmitCompleteEvent in " << txCompleteTime.GetSeconds () << "sec");
  Simulator::Schedule (txCompleteTime, &TrafficShaperNetDevice::LeakComplete, this, idx);

  m_macTxTrace (packet);

  //
  // If there's a transmission in progress, we enque the packet for later
  // transmission; otherwise we send it now.
  //
  if (m_txMachineState == READY) 
    {
      // 
      // Even if the transmitter is immediately available, we still enqueue and
      // dequeue the packet to hit the tracing hooks.
      //
      if (m_queue->Enqueue (packet) == true)
        {
          packet = m_queue->Dequeue ();
          m_snifferTrace (packet);
          m_promiscSnifferTrace (packet);
          return TransmitStart (packet);
        }
      else
        {
          // Enqueue may fail (overflow)
          m_macTxDropTrace (packet);
          return false;
        }
    }
  else
    {
      return m_queue->Enqueue(packet);
    }
}

  void 
TrafficShaperNetDevice::LeakComplete (int idx)
{
  NS_LOG_FUNCTION_NOARGS ();

  //
  // This function is called to when we're all done transmitting a packet.
  // We try and pull another packet off of the transmit queue.  If the queue
  // is empty, we are done, otherwise we need to start transmitting the
  // next packet.
  //
  
  Ptr<PathShaperState> pss = m_pathShapersMap[idx];
  NS_ASSERT_MSG(pss->pathQueueState == NLEAKING, "Must be BUSY if Leacking");
  pss->pathQueueState = LEAKING;

  Ptr<Packet> p = pss->pathQueue->Dequeue ();
  if (p == 0)
    {
      //
      // No packet was on the queue, so we just exit.
      //
      return;
    }

  LeakStart(p, idx);
}

PathShaperTag::PathShaperTag ()
{
}

PathShaperTag::~PathShaperTag ()
{}

TypeId
PathShaperTag::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::PathShaperTag")
    .SetParent<Tag> ()
    .AddConstructor<PathShaperTag> ()
    ;
  return tid;
}
TypeId
PathShaperTag::GetInstanceTypeId (void) const
{
  return GetTypeId ();
}

uint32_t 
PathShaperTag::GetSerializedSize (void) const
{
  return 4;
}

void 
PathShaperTag::Print (std::ostream &os) const
{
 os << "PATH_UTILIZATION" << m_pathId;
}

void
PathShaperTag::Serialize (TagBuffer start) const
{
  //Buffer::Iterator i = start;
 start.WriteU32 (m_pathId);

}

void
PathShaperTag::Deserialize (TagBuffer start)
{
  //Buffer::Iterator i = start;
 
  m_pathId  = start.ReadU32 ();
}


} // namespace ns3
